package cn.coder.toolkit;

import lombok.Getter;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public final class PasswordKit {

    public static void main(String[] args) {
        long begin = System.currentTimeMillis();
        String password = "eW2qw2ss3S11aa22t151j331";
        System.out.println(strength(password));
        System.out.println(check(password, category(false, false, true, true)));
        System.out.println(System.currentTimeMillis() - begin);
    }

    private PasswordKit() {}

    /**
     * 预定义的密码校验强度
     */
    public static final Feature[] WEAK = {length(1, 8)};
    public static final Feature[] MEDIUM = {length(8, 16)};
    public static final Feature[] STRONG = {length(8, 32), category(3)};
    public static final Feature[] COMPLEX = {length(8, 32), category(4), single(3), keyboard(2), logic(2), same(2)};
    public static final Feature[] UNPREDICTABLE = {length(16, 32), category(4), single(2), keyboard(2, true), logic(2, true), same(2, true)};

    /**
     * 预定义的密码校验强度
     */
    private enum Strength {
        NONE, ILLEGAL, WEAK, MEDIUM, STRONG, COMPLEX, UNPREDICTABLE;
    }

    private static final Pattern CONTAINS_DIGIT_PATTERN = Pattern.compile(".*[0-9].*");
    private static final Pattern CONTAINS_LOWER_PATTERN = Pattern.compile(".*[a-z].*");
    private static final Pattern CONTAINS_UPPER_PATTERN = Pattern.compile(".*[A-Z].*");
    private static final Pattern CONTAINS_SPECIAL_PATTERN = Pattern.compile(".*[`~!@#$%^&*()\\-_=+\\[\\]{}\\\\|;:'\",<>./?].*");
    private static final String SPECIAL_CHARACTER = "`~!@#$%^&*()-_=+[]{}\\|;:'\",<>./?";
    private static final Set<Character> SPECIAL_CHARACTER_SET = new HashSet<>();

    static {
        for (char c : SPECIAL_CHARACTER.toCharArray()) {
            SPECIAL_CHARACTER_SET.add(c);
        }
    }

    private static final List<String> KEYBOARD_CONTINUOUS_HORIZONTAL_LIST = Arrays.asList(
            "!@#$%^&*()_+", "01234567890", "qwertyuiop", "asdfghjkl", "zxcvbnm", "qwertyuiop".toUpperCase(), "asdfghjkl".toUpperCase(), "zxcvbnm".toUpperCase());
    private static final List<String> KEYBOARD_CONTINUOUS_OBLIQUE_LIST = Arrays.asList(
            "1qaz", "2wsx", "3edc", "4rfv", "5tgb", "6yhn", "7ujm", "8ik,", "9ol.", "0p;/", "=[;.", "-pl,", "0okm", "9ijn", "8uhb", "7ygv", "6tfc", "5rdx", "4esz",
            "!QAZ", "@WSX", "#EDC", "$RFV", "%TGB", "^YHN", "&UJM", "*IK<", "(OL>", ")P:?", "+{:>", "_PL<", ")OKM", "(IJN", "*UHB", "&YGV", "^TFC", "%RDX", "$ESZ");
    private static final List<String> KEYBOARD_CONTINUOUS_LIST = Stream.of(KEYBOARD_CONTINUOUS_HORIZONTAL_LIST, KEYBOARD_CONTINUOUS_OBLIQUE_LIST).flatMap(Collection::stream).collect(Collectors.toList());
    private static final List<String> LOGIC_CONTINUOUS_LIST = Arrays.asList(
            "!@#$%^&*()_+", "01234567890", "abcdefghijklmnopqrstuvwxyz", "abcdefghijklmnopqrstuvwxyz".toUpperCase());

    // ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------

    /**
     * 校验密码(暂未考虑空白等特殊字符)
     */
    public static Result check(String password, Feature... features) {
        // 未传入密码
        if (null == password || password.isEmpty()) {
            return Result.no("密码不应为空");
        }
        // 非法密码
        for (char c : password.toCharArray()) {
            if (!Character.isDigit(c) && !Character.isLowerCase(c) && !Character.isUpperCase(c) && !SPECIAL_CHARACTER_SET.contains(c)) {
                return Result.no(String.format("密码应只包含[0-9][a-z][A-Z]和[%s]", SPECIAL_CHARACTER));
            }
        }
        // 特征检测
        for (Feature feature : features) {
            Result result = feature.check(password);
            if (result.isNotPassed()) {
                return result;
            }
        }
        // 检测通过
        return Result.ok();
    }

    /**
     * 校验密码, 默认要求强度为强壮
     */
    public static Result check(String password) {
        return check(password, STRONG);
    }

    /**
     * 密码强度(在所有验证都开启的情况下,每满足一个小条件加1级)
     */
    public static String strength(String password) {
        // 未传入密码
        if (null == password || password.isEmpty()) {
            return Strength.NONE.name();
        }
        // 非法密码
        for (char c : password.toCharArray()) {
            if (!Character.isDigit(c) && !Character.isLowerCase(c) && !Character.isUpperCase(c) && !SPECIAL_CHARACTER_SET.contains(c)) {
                return Strength.ILLEGAL.name();
            }
        }
        // 强度校验
        if (check(password, UNPREDICTABLE).passed) {
            return Strength.UNPREDICTABLE.name();
        } else if (check(password, COMPLEX).passed) {
            return Strength.COMPLEX.name();
        } else if (check(password, STRONG).passed) {
            return Strength.STRONG.name();
        } else if (check(password, MEDIUM).passed) {
            return Strength.MEDIUM.name();
        } else if (check(password, WEAK).passed) {
            return Strength.WEAK.name();
        }
        return Strength.NONE.name();
    }

    // ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------

    public static FeatureLength length(int min, int max) {
        FeatureLength feature = new FeatureLength();
        feature.min = min;
        feature.max = max;
        return feature;
    }

    public static FeatureCategory category(int min) {
        FeatureCategory feature = new FeatureCategory();
        feature.min = min;
        return feature;
    }

    public static FeatureCategory category(boolean digit, boolean lower, boolean upper, boolean special) {
        FeatureCategory feature = new FeatureCategory();
        feature.digit = digit;
        feature.lower = lower;
        feature.upper = upper;
        feature.special = special;
        return feature;
    }

    public static FeatureCategoryContinuous single(int max) {
        FeatureCategoryContinuous feature = new FeatureCategoryContinuous();
        feature.max = max;
        return feature;
    }

    public static FeatureKeyboardContinuous keyboard(int max, boolean ignoreCase) {
        FeatureKeyboardContinuous feature = new FeatureKeyboardContinuous();
        feature.max = max;
        feature.ignoreCase = ignoreCase;
        return feature;
    }

    public static FeatureKeyboardContinuous keyboard(int max) {
        return keyboard(max, false);
    }

    public static FeatureLogicContinuous logic(int max, boolean ignoreCase) {
        FeatureLogicContinuous feature = new FeatureLogicContinuous();
        feature.max = max;
        feature.ignoreCase = ignoreCase;
        return feature;
    }

    public static FeatureLogicContinuous logic(int max) {
        return logic(max, false);
    }

    public static FeatureSameContinuous same(int max, boolean ignoreCase) {
        FeatureSameContinuous feature = new FeatureSameContinuous();
        feature.max = max;
        feature.ignoreCase = ignoreCase;
        return feature;
    }

    public static FeatureSameContinuous same(int max) {
        return same(max, false);
    }

    public static FeatureCustomerList custom(Set<String> set) {
        FeatureCustomerList feature = new FeatureCustomerList();
        feature.set = set;
        return feature;
    }

    // ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------

    /**
     * 校验结果
     */
    @Getter
    public static class Result {

        private boolean passed; // 是否通过
        private String reason; // 未通过原因

        private Result() {}

        public static Result ok() {
            Result result = new Result();
            result.passed = true;
            return result;
        }

        public static Result no(String reason) {
            Result result = new Result();
            result.passed = false;
            result.reason = reason;
            return result;
        }

        public boolean isNotPassed() {
            return !this.passed;
        }

        @Override
        public String toString() {
            return String.format("Result: passed:%s" + (this.passed ? "" : ", reason:%s"), this.passed, this.reason);
        }
    }

    public interface Feature {
        Result check(String password);
    }

    /**
     * 密码长度验证
     */
    public static class FeatureLength implements Feature {
        private int min; // 最小长度
        private int max; // 最大长度

        @Override
        public Result check(String password) {
            int length = password.length();
            boolean pass = this.min <= length && length <= this.max;
            return pass ? Result.ok() : Result.no(String.format("密码长度应在%d位到%d位之间", this.min, this.max));
        }
    }

    /**
     * 密码字符种类验证
     */
    public static class FeatureCategory implements Feature {
        private int min; // 至少包含种类, 有该配置则不校验下列配置(用户传了两个进来的情况除外)
        private boolean digit; // 数字
        private boolean lower; // 小写字母
        private boolean upper; // 大写字母
        private boolean special; // 特殊符号

        @Override
        public Result check(String password) {
            // 校验是否包含指定类型
            boolean containsDigit = CONTAINS_DIGIT_PATTERN.matcher(password).matches();
            boolean containsLower = CONTAINS_LOWER_PATTERN.matcher(password).matches();
            boolean containsUpper = CONTAINS_UPPER_PATTERN.matcher(password).matches();
            boolean containsSpecial = CONTAINS_SPECIAL_PATTERN.matcher(password).matches();
            // 如果是要求至少含有几种类型
            if (this.min != 0) {
                int min = Math.min(this.min, 4);
                boolean pass = count(containsDigit, containsLower, containsUpper, containsSpecial) >= min;
                return pass ? Result.ok() : Result.no(String.format("密码种类应包含[0-9][a-z][A-Z]和[%s]中的至少%d种", SPECIAL_CHARACTER, this.min));
            }
            // 如果是指定包含某些类型
            boolean checkDigitPass = !this.digit || containsDigit;
            boolean checkLowerPass = !this.lower || containsLower;
            boolean checkUpperPass = !this.upper || containsUpper;
            boolean checkSpecialPass = !this.special || containsSpecial;
            boolean pass = checkDigitPass && checkLowerPass && checkUpperPass && checkSpecialPass;
            if (pass) {
                return Result.ok();
            } else {
                if (!checkDigitPass) {
                    return Result.no("密码应包含[0-9]");
                } else if (!checkLowerPass) {
                    return Result.no("密码应包含[a-z]");
                } else if (!checkUpperPass) {
                    return Result.no("密码应包含[A-Z]");
                } else if (!checkSpecialPass) {
                    return Result.no(String.format("密码应包含[%s]", SPECIAL_CHARACTER));
                }
                return Result.no("type check");
            }
        }
    }

    /**
     * 密码类别连续个数验证
     */
    public static class FeatureCategoryContinuous implements Feature {
        private int max; // 最大允许连续个数

        @Override
        public Result check(String password) {
            for (int i = 0; i + this.max + 1 <= password.length(); i++) {
                String original = password.substring(i, i + this.max + 1);
                char first = original.charAt(0);
                if (Character.isDigit(first)) {
                    int count = 0;
                    for (int j = 1; j < original.length(); j++) {
                        if (Character.isDigit(original.charAt(j))) {
                            count++;
                        }
                    }
                    if (count == this.max) {
                        return Result.no(String.format("密码不应包含连续同类字符[%s]", original));
                    }
                }
                if (Character.isLowerCase(first)) {
                    int count = 0;
                    for (int j = 1; j < original.length(); j++) {
                        if (Character.isLowerCase(original.charAt(j))) {
                            count++;
                        }
                    }
                    if (count == this.max) {
                        return Result.no(String.format("密码不应包含连续同类字符[%s]", original));
                    }
                }
                if (Character.isUpperCase(first)) {
                    int count = 0;
                    for (int j = 1; j < original.length(); j++) {
                        if (Character.isUpperCase(original.charAt(j))) {
                            count++;
                        }
                    }
                    if (count == this.max) {
                        return Result.no(String.format("密码不应包含连续同类字符[%s]", original));
                    }
                }
                if (SPECIAL_CHARACTER_SET.contains(first)) {
                    int count = 0;
                    for (int j = 1; j < original.length(); j++) {
                        if (SPECIAL_CHARACTER_SET.contains(original.charAt(j))) {
                            count++;
                        }
                    }
                    if (count == this.max) {
                        return Result.no(String.format("密码不应包含连续同类字符[%s]", original));
                    }
                }
            }
            return Result.ok();
        }
    }

    /**
     * 键盘按键连续个数验证
     * qwertyu,1qaz
     */
    public static class FeatureKeyboardContinuous implements Feature {
        private int max; // 最大允许连续个数
        private boolean ignoreCase; // 忽略大小写

        @Override
        public Result check(String password) {
            for (int i = 0; i + this.max + 1 <= password.length(); i++) {
                String original = password.substring(i, i + this.max + 1);
                String originalLower = original.toLowerCase();
                String reverse = new StringBuilder(original).reverse().toString();
                String reverseLower = reverse.toLowerCase();
                for (String sequence : KEYBOARD_CONTINUOUS_LIST) {
                    if (sequence.contains(this.ignoreCase ? originalLower : original)) {
                        return Result.no(String.format("密码不应包含连续字符[%s]", original));
                    } else if (sequence.contains(this.ignoreCase ? reverseLower : reverse)) {
                        return Result.no(String.format("密码不应包含连续字符[%s]", original));
                    }
                }
            }
            return Result.ok();
        }
    }

    /**
     * 逻辑字符连续个数验证
     * 123456,abcdefg
     */
    public static class FeatureLogicContinuous implements Feature {
        private int max; // 最大允许连续个数
        private boolean ignoreCase; // 忽略大小写

        @Override
        public Result check(String password) {
            for (int i = 0; i + this.max + 1 <= password.length(); i++) {
                String original = password.substring(i, i + this.max + 1);
                String originalLower = original.toLowerCase();
                String reverse = new StringBuilder(original).reverse().toString();
                String reverseLower = reverse.toLowerCase();
                for (String sequence : LOGIC_CONTINUOUS_LIST) {
                    if (sequence.contains(this.ignoreCase ? originalLower : original)) {
                        return Result.no(String.format("密码不应包含连续字符[%s]", original));
                    } else if (sequence.contains(this.ignoreCase ? reverseLower : reverse)) {
                        return Result.no(String.format("密码不应包含连续字符[%s]", original));
                    }
                }
            }
            return Result.ok();
        }
    }

    /**
     * 相邻相同连续个数验证
     * 11111,sssss
     */
    public static class FeatureSameContinuous implements Feature {
        private int max; // 最大允许连续个数
        private boolean ignoreCase; // 忽略大小写

        @Override
        public Result check(String password) {
            for (int i = 0; i + this.max + 1 <= password.length(); i++) {
                String original = password.substring(i, i + this.max + 1);
                String originalLower = original.toLowerCase();
                int count = 0;
                for (int j = 1; j < original.length(); j++) {
                    String temp = this.ignoreCase ? originalLower : original;
                    if (temp.charAt(0) == temp.charAt(j)) {
                        count++;
                    }
                }
                if (count == this.max) {
                    return Result.no(String.format("密码不应包含连续字符[%s]", original));
                }
            }
            return Result.ok();
        }
    }

    /**
     * 自定义密码表验证
     */
    public static class FeatureCustomerList implements Feature {
        private Set<String> set; // 自定义的密码表

        @Override
        public Result check(String password) {
            if (set.contains(password)) {
                return Result.no(String.format("密码不应是[%s]", password));
            }
            return Result.ok();
        }
    }

    // ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------

    private static int count(boolean... elements) {
        int count = 0;
        for (boolean element : elements) {
            if (element) {
                count++;
            }
        }
        return count;
    }

}
